Skip to main content

Deployment

Léigh Dom deploys like every other user-facing ABAIR app: CI builds an image on docker-build, pushes it to the internal registry, and SSHes into services-user to restart the stack. Merging to main is the whole release process.

Repositoryphonlab-tcd/leigh-dom
Image10.0.0.12:5000/leigh-dom:main
Hostservices-user (10.0.0.18), port 3015 → container 3000
Compose stack/opt/apps/leigh-dom/
Public URLhttps://leighdom.abair.ie
StageTriggerWhat happens
CIPR to mainnpm ci, typecheck, lint, tests with coverage, and a production next build.
CD — buildPush to mainBuilds the image on the self-hosted runner and pushes it to the internal registry.
CD — deployAfter the build jobssh deploy@10.0.0.18 "cd /opt/apps/leigh-dom && docker compose pull && docker compose up -d".

There is no registry login step: the github-runner user on docker-build is pre-authenticated by setup-docker-build.yml. Both jobs run in the prod GitHub environment, which holds the build-time variables.

Configuration

NEXT_PUBLIC_* values referenced from client components are inlined into the browser bundle by next build — setting them only in the compose environment: block leaves the browser with undefined. The sign-in link, account menu and 401 recovery all run client-side, so their config has to arrive as build args.

VariableSet whereNotes
NEXT_PUBLIC_SUPABASE_URLBuild argFrom vars.* in the prod environment
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYBuild argPublic by design
NEXT_PUBLIC_AUTH_BASE_URLBuild arghttps://auth.abair.ie
NEXT_PUBLIC_SITE_URLBothRead again server-side by the middleware and /auth/callback, so the two must match
PUBLIC_ORIGINComposehttps://leighdom.abair.ie
RETENTION_DAYSCompose30

Everything else keeps the defaults in lib/config.ts.

warning

PUBLIC_ORIGIN is the one value that must not be left at its default. It is sent as Origin and Referer to synthesis.abair.ie, which rejects an empty or mismatched value — its default of http://localhost:3000 fails every synthesis request in production while the app looks perfectly healthy.

No .env

Nothing Léigh Dom reads at run time is secret, so it is not in user_apps_with_env and has no Vaultwarden note. If that changes, add it there and create a leigh-dom .env note rather than templating the value into git — the convention abair_website, geabaire-api, giob-geab and mao-teacher-portal follow.

Persistence

The stack declares a named data volume rather than a bind mount, so there is nothing to rsync in on first deploy: Docker creates it on the first up -d and the entrypoint creates the subtrees. pull && up -d leaves it intact; only down -v destroys it.

caution

A named volume is not picked up by anything backing up /opt/apps. Treat the contents as regenerable — a lost volume costs users their document history, not the system its configuration.

nginx

networking/nginx-config/leighdom.abair.ie needs two things the standard template does not have:

  • client_max_body_size 32M. The app's cap is 25 MiB; nginx's 1 MB default would reject every upload with a bare 413 first. 32 MiB sits just above the app's cap, so an oversized file gets the app's error instead.
  • proxy_http_version 1.1 with Connection cleared. The progress stream is Server-Sent Events, and nginx defaults to HTTP/1.0 upstream, which drops the keep-alive it relies on.

No buffering or timeout overrides are needed — the app sends X-Accel-Buffering: no and its own 15-second keep-alive.

Checking what is deployed

/api/health answers more than liveness, and 503 when the database or a statvfs call fails:

curl -s https://leighdom.abair.ie/api/health | jq
{
"ok": true,
"db": "ok",
"diskFreeBytes": 8123456789,
"runner": { "running": true, "activeJobs": 0 },
"video": { "available": true },
"queue": { "oldestQueuedAgeS": 0 }
}

runner and queue separate "healthy and idle" from "healthy-looking but backing up": oldestQueuedAgeS climbing while runner.running is false means the worker died without taking the process down. video.available: false means ffmpeg is missing — the client hides the export rather than offering a button that always fails, so check the probe, not the UI.

On the host:

ssh services-user-vm "cd /opt/apps/leigh-dom && docker compose ps && docker compose logs --tail 50"

Adding it to a fresh estate

  1. ansible/hetzner-main/services-user-apps/leigh-dom/docker-compose.yml — the stack.
  2. leigh-dom in the user_apps list in setup-services-user.yml.
  3. networking/nginx-config/leighdom.abair.ie — the vhost.
  4. Deploy:
    make deploy-services-user-apps
    make update-webserver
  5. A Cloudflare DNS record for leighdom. The wildcard *.abair.ie certificate already covers it.
  6. Apply supabase/0001_lei_uploads.sql once against the shared auth project. Idempotent, and the app degrades gracefully without it.

No allow-list entry is needed in the auth app: TRUSTED_BASE_DOMAINS = ["abair.ie"] in src/config/authOrigins.js already trusts every https://*.abair.ie origin. See Authentication.

Last updated 2026-09-16